//! Core traits for working with execution payloads.

use crate::PayloadBuilderError;
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_eips::{
    eip4895::{Withdrawal, Withdrawals},
    eip7685::Requests,
};
use alloy_primitives::{Address, B256, U256};
use alloy_rpc_types_engine::{PayloadAttributes as EthPayloadAttributes, PayloadId};
use core::fmt;
use either::Either;
use reth_chain_state::ComputedTrieData;
use reth_execution_types::ExecutionOutcome;
use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader};
use reth_trie_common::{
    updates::{TrieUpdates, TrieUpdatesSorted},
    HashedPostState, HashedPostStateSorted,
};

/// Represents an executed block for payload building purposes.
///
/// This type captures the complete execution state of a built block,
/// including the recovered block, execution outcome, hashed state, and trie updates.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BuiltPayloadExecutedBlock<N: NodePrimitives> {
    /// Recovered Block
    pub recovered_block: Arc<RecoveredBlock<N::Block>>,
    /// Block's execution outcome.
    pub execution_output: Arc<ExecutionOutcome<N::Receipt>>,
    /// Block's hashed state.
    ///
    /// Supports both unsorted and sorted variants so payload builders can avoid cloning in order
    /// to convert from one to the other when it's not necessary.
    pub hashed_state: Either<Arc<HashedPostState>, Arc<HashedPostStateSorted>>,
    /// Trie updates that result from calculating the state root for the block.
    ///
    /// Supports both unsorted and sorted variants so payload builders can avoid cloning in order
    /// to convert from one to the other when it's not necessary.
    pub trie_updates: Either<Arc<TrieUpdates>, Arc<TrieUpdatesSorted>>,
}

impl<N: NodePrimitives> BuiltPayloadExecutedBlock<N> {
    /// Converts this into an [`reth_chain_state::ExecutedBlock`].
    ///
    /// Ensures hashed state and trie updates are in their sorted representations
    /// as required by `reth_chain_state::ExecutedBlock`.
    pub fn into_executed_payload(self) -> reth_chain_state::ExecutedBlock<N> {
        let hashed_state = match self.hashed_state {
            // Convert unsorted to sorted
            Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()),
            // Already sorted
            Either::Right(sorted) => sorted,
        };

        let trie_updates = match self.trie_updates {
            // Convert unsorted to sorted
            Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()),
            // Already sorted
            Either::Right(sorted) => sorted,
        };

        reth_chain_state::ExecutedBlock::new(
            self.recovered_block,
            self.execution_output,
            ComputedTrieData::without_trie_input(hashed_state, trie_updates),
        )
    }
}

/// Represents a successfully built execution payload (block).
///
/// Provides access to the underlying block data, execution results, and associated metadata
/// for payloads ready for execution or propagation.
#[auto_impl::auto_impl(&, Arc)]
pub trait BuiltPayload: Send + Sync + fmt::Debug {
    /// The node's primitive types
    type Primitives: NodePrimitives;

    /// Returns the built block in its sealed (hash-verified) form.
    fn block(&self) -> &SealedBlock<<Self::Primitives as NodePrimitives>::Block>;

    /// Returns the total fees collected from all transactions in this block.
    fn fees(&self) -> U256;

    /// Returns the complete execution result including state updates.
    ///
    /// Returns `None` if execution data is not available or not tracked.
    fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
        None
    }

    /// Returns the EIP-7685 execution layer requests included in this block.
    ///
    /// These are requests generated by the execution layer that need to be
    /// processed by the consensus layer (e.g., validator deposits, withdrawals).
    fn requests(&self) -> Option<Requests>;
}

/// Attributes used to guide the construction of a new execution payload.
///
/// Extends basic payload attributes with additional context needed during the
/// building process, tracking in-progress payload jobs and their parameters.
pub trait PayloadBuilderAttributes: Send + Sync + Unpin + fmt::Debug + 'static {
    /// The external payload attributes format this type can be constructed from.
    type RpcPayloadAttributes: Send + Sync + 'static;
    /// The error type used in [`PayloadBuilderAttributes::try_new`].
    type Error: core::error::Error + Send + Sync + 'static;

    /// Constructs new builder attributes from external payload attributes.
    ///
    /// Validates attributes and generates a unique [`PayloadId`] based on the
    /// parent block, attributes, and version.
    fn try_new(
        parent: B256,
        rpc_payload_attributes: Self::RpcPayloadAttributes,
        version: u8,
    ) -> Result<Self, Self::Error>
    where
        Self: Sized;

    /// Returns the unique identifier for this payload build job.
    fn payload_id(&self) -> PayloadId;

    /// Returns the hash of the parent block this payload builds on.
    fn parent(&self) -> B256;

    /// Returns the timestamp to be used in the payload's header.
    fn timestamp(&self) -> u64;

    /// Returns the beacon chain block root from the parent block.
    ///
    /// Returns `None` for pre-merge blocks or non-beacon contexts.
    fn parent_beacon_block_root(&self) -> Option<B256>;

    /// Returns the address that should receive transaction fees.
    fn suggested_fee_recipient(&self) -> Address;

    /// Returns the randomness value for this block.
    fn prev_randao(&self) -> B256;

    /// Returns the list of withdrawals to be processed in this block.
    fn withdrawals(&self) -> &Withdrawals;
}

/// Basic attributes required to initiate payload construction.
///
/// Defines minimal parameters needed to build a new execution payload.
/// Implementations must be serializable for transmission.
pub trait PayloadAttributes:
    serde::de::DeserializeOwned + serde::Serialize + fmt::Debug + Clone + Send + Sync + 'static
{
    /// Returns the timestamp for the new payload.
    fn timestamp(&self) -> u64;

    /// Returns the withdrawals to be included in the payload.
    ///
    /// `Some` for post-Shanghai blocks, `None` for earlier blocks.
    fn withdrawals(&self) -> Option<&Vec<Withdrawal>>;

    /// Returns the parent beacon block root.
    ///
    /// `Some` for post-merge blocks, `None` for pre-merge blocks.
    fn parent_beacon_block_root(&self) -> Option<B256>;
}

impl PayloadAttributes for EthPayloadAttributes {
    fn timestamp(&self) -> u64 {
        self.timestamp
    }

    fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
        self.withdrawals.as_ref()
    }

    fn parent_beacon_block_root(&self) -> Option<B256> {
        self.parent_beacon_block_root
    }
}

#[cfg(feature = "op")]
impl PayloadAttributes for op_alloy_rpc_types_engine::OpPayloadAttributes {
    fn timestamp(&self) -> u64 {
        self.payload_attributes.timestamp
    }

    fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
        self.payload_attributes.withdrawals.as_ref()
    }

    fn parent_beacon_block_root(&self) -> Option<B256> {
        self.payload_attributes.parent_beacon_block_root
    }
}

/// Factory trait for creating payload attributes.
///
/// Enables different strategies for generating payload attributes based on
/// contextual information. Useful for testing and specialized building.
pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
    Send + Sync + 'static
{
    /// Constructs new payload attributes for the given timestamp.
    fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
}

impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
where
    Header: Clone,
    F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
{
    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
        self(parent.clone())
    }
}

impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
where
    L: PayloadAttributesBuilder<Attributes, Header>,
    R: PayloadAttributesBuilder<Attributes, Header>,
{
    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
        match self {
            Self::Left(l) => l.build(parent),
            Self::Right(r) => r.build(parent),
        }
    }
}

impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
    for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
where
    Header: 'static,
    Attributes: 'static,
{
    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
        self.as_ref().build(parent)
    }
}

/// Trait to build the EVM environment for the next block from the given payload attributes.
///
/// Accepts payload attributes from CL, parent header and additional payload builder context.
pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
    /// Builds the EVM environment for the next block from the given payload attributes.
    fn build_next_env(
        attributes: &Attributes,
        parent: &SealedHeader<Header>,
        ctx: &Ctx,
    ) -> Result<Self, PayloadBuilderError>;
}
